import CompEle from "../ele/CompEle";
import {clone} from "../util";
import Recycle from "../struts/Recycle";

export default class ViewRouter {
    constructor (view, okay,config) {
        this.routers = this.parse_rule(config.route)
        this.name = config.name
        this.recycle = config.recycle || true //组件是否回收
        this.recyle_size = config.recyle_size || 8 //组件回收站大小
        this.current = 0 //指向当前组件
        this.index = ''
        this.historyStack = [] //记录组件历史
        this.componentRecycle = new Recycle(this.recyle_size) //组件回收站，站满时按时间顺序删除组件
        this.okay = okay
        this.view = view
        this.current_url = ''
        this.init()
    }
    init () {
        let route = this.routers[0]
        this.route(route.url, true)
        this.current_url = route.url
    }
    show (component) {
        this.view.innerHTML = ''
        this.view.appendChild(component.ele)
    }

    route (url,is_new = false) {
        // is_new 是否新建组件，
        // 如果为false，将从历史组件中或回收站中获取组件
        // 如果历史记录中已存在组件，则组件回到栈顶，
        // 在该组件之上的组件将被弹出，进入组件回收站
        // route函数都将当前指针指向栈顶
        let route = this.check_rule(url)
        let comp_map = null
        if (is_new) {
            comp_map = this.newComp(url, route)
        } else {
            //从历史记录中检索
            let index = -1
            for (let i = 0; i < this.historyStack.length; i++) {
                if (route.rule.source == [...this.historyStack[i].keys()][0].source) {
                    comp_map = this.historyStack[i]
                    index = i
                    break
                }
            }
            if (comp_map == null) {
                if (this.recycle) {
                    for (let comp_m of this.componentRecycle) {
                        if (route.rule.source == [...comp_m.keys()][0].source) {
                            comp_map = comp_m
                            break
                        }
                    }
                    if (comp_map == null) {
                        comp_map = this.newComp(url, route)
                    }else {
                        this.componentRecycle.recycle(comp_map)
                        let comp_ele = [...comp_map.values()][0]
                        if (comp_ele.okays[0].on_restart) {
                            comp_ele.okays[0].on_restart.call(comp_ele.okays[0])
                        }
                        while (this.current < this.historyStack.length - 1) {
                            let comp = this.historyStack.pop()
                            let ele = [...comp.values()][0]
                            if(ele.okays[0].on_stop ) {
                                ele.okays[0].on_stop.call(ele.okays[0])
                            }
                            if (this.recycle) {
                                this.componentRecycle.add(comp)
                            }else {
                                if(ele.okays[0].on_destroy ) {
                                    ele.okays[0].on_destroy.call(ele.okays[0])
                                }
                            }
                        }
                        this.historyStack.push(comp_map)
                        let okay = [...comp_map.values()][0].okays[0]
                        if (okay.on_resume) {
                            okay.on_resume.call(okay)
                        }
                        this.current = this.historyStack.length - 1
                    }
                }else {
                    comp_map = this.newComp(url, route)
                }
            }else {
                if (index > this.current) {
                    comp_map = this.historyStack[index]
                    while (this.current < this.historyStack.length - 1) {
                        let comp = this.historyStack.pop()
                        let ele = [...comp.values()][0]
                        if(ele.okays[0].on_stop ) {
                            ele.okays[0].on_stop.call(ele.okays[0])
                        }
                        if (this.recycle) {
                            this.componentRecycle.add(comp)
                        }else {
                            if(ele.okays[0].on_destroy ) {
                                ele.okays[0].on_destroy.call(ele.okays[0])
                            }
                        }
                    }
                    this.historyStack.push(comp_map)
                    let okay = [...comp_map.values()][0].okays[0]
                    if (okay.on_resume) {
                        okay.on_resume.call(okay)
                    }
                    this.current = this.historyStack.length - 1
                }
            }
        }
        let values = url.match(route.rule)
        if(values != null) values.shift()
        let comp = [...comp_map.values()][0]
        let matches = route.url.match(/\/:[^\/]+/g)
        if (matches) {
            let i = 0
            matches.forEach((match) => {
                let p = match.replace('/:', '')
                comp.okays[0].data.$route[p] = values[i]
                i++
            })
        }
        this.current_url = url
        if (route.on_route != null) {
            route.on_route.call(this.okay, this.current > 0?[...this.historyStack[this.current - 1].values()][0].subject:null, comp.subject, route)
        }
        this.show(comp)
    }
    newComp (url, route) {
        let tagName = route.Component.name || 'comp'
        let ele = document.createElement(tagName)
        let matches = route.url.match(/\/:[^\/]+/g)
        let values = url.match(route.rule)
        if(values != null) values.shift()
        let params = clone(route.param)
        if (matches) {
            let i = 0
            matches.forEach((match) => {
                let p = match.replace('/:', '')
                params[p] = values[i]
                i++
            })
        }

        let data_fn = route.Component.data?
            new Function('data', 'route' ,'return function(){ data.$route = route\n return data}')(route.Component.data(), params)
            :new Function('route','return function() { return {$route: route}}')(params)
        route.Component.data = data_fn
        let comp_ele = new CompEle(tagName, this.okay, ele,route.Component)
        let comp_map = new Map()
        comp_map.set(route.rule, comp_ele)
        // 弹出所有在不在栈顶的组件，进入回收站
        while (this.current < this.historyStack.length - 1) {
            let comp = this.historyStack.pop()
            if (this.recycle) {
                this.componentRecycle.add(comp)
            }
        }
        this.historyStack.push(comp_map)
        this.current = this.historyStack.length - 1
        return comp_map
    }
    can (index) {
        if (this.current + index > 0 && this.current + index < this.historyStack.length) {
            return true
        }
        return false
    }
    go (index) {
        let current_comp = this.historyStack[this.current]
        let okay = [...current_comp.values()][0].okays[0]
        if (okay.on_pause) {
            okay.on_pause.call(okay)
        }
        if (this.current + index < 0) {
            this.current = 0
        }else if (this.current + index > this.historyStack.length - 1) {
            this.current = this.historyStack.length - 1
        }else {
            this.current = this.current + index
        }
        let component_map = this.historyStack[this.current]
        let comp = [...component_map.values()][0]
        if (comp.okays[0].on_resume) {
            comp.okays[0].on_resume.call(comp.okays[0])
        }
        this.show(comp)
    }

    /**
     * 解析路由规则，提取路由变量，路由匹配正则表达式
     * @param config
     */
    parse_rule (config) {
        let routes = []
        config.forEach((route) => {
            let path = route.path
            let rule = new RegExp('^' + path.replace(/\/:[^\/]+/g, '/([^/]+)') + '$')
            let param = {}
            let matches = path.match(/\/:[^\/]+/g)
            if (matches) {
                matches.forEach((match) => {
                    let p = match.replace('/:','')
                    param[p] = ''
                })
            }
            let item = {
                url: route.path,
                rule: rule,
                Component:route.Component,
                param: param,
                on_route: route.on_route || null
            }
            routes.push(item)
        })
        return routes
    }
    check_rule (url) {
        for (let route of this.routers) {
            if(route.rule.test(url)) {
                return route
            }
        }
        throw '未找到路由路径'
    }
}